package ro.toporjinschi.androidorm;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

public class Query<T extends Entity> {
    private static final String PATTERN_BEGIN = "(\\{";
    private static final String PATTERN_END = "\\})";
    private static final String PATTERN = "(\\{(.*?)\\})";
    private final EntityMetadata entityMaping;
    private String query;

    private final SQLiteDatabase db;

    public Query(EntityMetadata entityMaping, String nqb, SQLiteDatabase db) {
        this.entityMaping = entityMaping;
        this.query = nqb;
        this.db = db;
    }

    public void setParameter(String parameter, String value) {
        replacePlaceHolder(parameter, value);
    }

    public void setParameter(String parameter, Integer value) {
        replacePlaceHolder(parameter, String.valueOf(value));
    }

    public void setParameter(String parameter, Long value) {
        replacePlaceHolder(parameter, String.valueOf(value));
    }

    public void setParameter(String parameter, Short value) {
        replacePlaceHolder(parameter, String.valueOf(value));
    }

    public void setParameter(String parameter, Boolean value) {
        replacePlaceHolder(parameter, String.valueOf(value));
    }

    public void setParameter(String parameter, Double value) {
        replacePlaceHolder(parameter, String.valueOf(value));
    }

    public void setParameter(String parameter, Float value) {
        replacePlaceHolder(parameter, String.valueOf(value));
    }

    private void replacePlaceHolder(String parameter, String value) {
        final Pattern patt = Pattern.compile(PATTERN_BEGIN + parameter + PATTERN_END);
        final Matcher m = patt.matcher(this.query);
        this.query = m.replaceAll(value);
    }

    public List<T> getResult() {
        checkPlaceHolders();
        final Cursor cursor = this.db.rawQuery(this.query, null);
        return getResult(cursor, this.entityMaping.getClazz(), this.entityMaping);
    }

    private void checkPlaceHolders() {
        final Pattern patt = Pattern.compile(PATTERN);
        final Matcher m = patt.matcher(this.query);
        StringBuilder placeHolders = new StringBuilder();
        while (m.find()) {
            placeHolders.append(m.group(0)).append(", ");
        }
        if (placeHolders.length() > 0) {
            placeHolders.setLength(placeHolders.length() - 2);
            throw new RuntimeException("The parameters :" + placeHolders.toString() + " are not specified! Did you called setParameter for them?");
        }
    }

    @SuppressWarnings("unchecked")
    private List<T> getResult(Cursor cursor, Class<? extends Entity> clazz, EntityMetadata em) {
        final List<T> results = new ArrayList<T>();
        while (cursor.moveToNext()) {
            try {
                final Entity entInstance = clazz.newInstance();
                final String[] colSelected = cursor.getColumnNames();
                for (int i = 0; i < colSelected.length; i++) {
                    final FieldMetaData field = em.getFieldMetaData(colSelected[i]);
                    setFieldValue(entInstance, field, cursor);
                }
                results.add((T) entInstance);
            } catch (final IllegalArgumentException e) {
                throw new RuntimeException(e);
            } catch (final IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (final InstantiationException e) {
                throw new RuntimeException(e);
            }
        }
        return results;
    }

    private void setFieldValue(Entity entInstance, FieldMetaData field, Cursor cursor) {
        final String fieldDbName = field.getDbName();
        try {
            Object value = null;
            if (field.getType() == String.class) {
                value = cursor.getString(cursor.getColumnIndex(fieldDbName));
            } else if (field.getType() == Integer.class) {
                value = cursor.getInt(cursor.getColumnIndex(fieldDbName));
            } else if (field.getType() == Boolean.class) {
                value = cursor.getInt(cursor.getColumnIndex(fieldDbName)) != 0;
            } else if (field.getType() == Float.class) {
                value = cursor.getFloat(cursor.getColumnIndex(fieldDbName));
            } else if (field.getType() == Short.class) {
                value = cursor.getShort(cursor.getColumnIndex(fieldDbName));
            } else if (field.getType() == Long.class) {
                value = cursor.getLong(cursor.getColumnIndex(fieldDbName));
            } else if (field.getType() == Double.class) {
                value = cursor.getDouble(cursor.getColumnIndex(fieldDbName));
            }
            field.getSetMethod().invoke(entInstance, value);
        } catch (final IllegalArgumentException e) {
            e.printStackTrace();
        } catch (final IllegalAccessException e) {
            e.printStackTrace();
        } catch (final InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}